#include "KKApplication.h"
#include "KKComm.h"
#include "KKWindow.h"
#include <winuser.h>

static void *g_instance = nullptr;
extern KKApplication *g_app;

#define USER_DATA_HDC 100

static int _register_win_class(HINSTANCE hInstance, const wchar_t *className, WNDPROC wndProc, int brush = WHITE_BRUSH)
{
    WNDCLASSEXW wc;
    wc.cbSize = sizeof(WNDCLASSEXW);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.lpfnWndProc = wndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)::GetStockObject(brush);
    wc.lpszMenuName = nullptr;
    wc.lpszClassName = className;
    wc.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
    return ::RegisterClassExW(&wc);
}

static LRESULT CALLBACK _WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    auto win = (KKWindow *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (win != nullptr)
    {
        auto bProcessed = win->_MessageTranslate(hwnd, msg, wparam, lparam);
        if (bProcessed)
        {
            return true;
        }
    }
    return ::DefWindowProcW(hwnd, msg, wparam, lparam);
}

// 只处理基础的Win32消息，然后封装为 KKEvent 消息，转发到 KKApplication
bool KKWindow::_MessageTranslate(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    bool bProcessed = false;
    KKEvent event;
    event.msg_native = msg;

    switch (msg)
    {
    case WM_CREATE:
    {
        ::SetFocus(hwnd);
        // SetTimer(hwnd, 1, 500, nullptr);
    }
    break;
    case WM_TIMER:
    {
        event.msg = KK_WIN_MSG_TIMER;
        event.ready = true;
    }
    break;
    case WM_DESTROY:
        event.msg = KK_WIN_MSG_DESTROY;
        event.ready = true;
        break;
    case WM_CLOSE:
        event.msg = KK_WIN_MSG_CLOSE;
        event.ready = true;
        break;
    case WM_SETCURSOR:
        // https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-nchittest
        if (LOWORD(lparam) == HTCLIENT)
        {
            // SetCursor(KK_CURSOR_STYLE_HELP);
            //  bProcessed = true;
        }
        break;
    case WM_MOVE:
    {
        auto x = LOWORD(lparam);
        auto y = HIWORD(lparam);
        m_rect.x2 = x + (m_rect.x2 - m_rect.x1);
        m_rect.y2 = y + (m_rect.y2 - m_rect.y1);
        m_rect.x1 = x;
        m_rect.y1 = y;
        event.msg = KK_WIN_MSG_MOVE;
        event.ready = true;
    }
    break;
    case WM_SIZE:
    {
        RECT rc;
        // window rect
        ::GetWindowRect(hwnd, &rc);
        m_rect.x1 = rc.left;
        m_rect.y1 = rc.top;
        m_rect.x2 = rc.right;
        m_rect.y2 = rc.bottom;
        // client size
        ::GetClientRect(hwnd, &rc);
        m_rect_client.x1 = rc.left;
        m_rect_client.y1 = rc.top;
        m_rect_client.x2 = rc.right;
        m_rect_client.y2 = rc.bottom;

        event.msg = KK_WIN_MSG_RESIZE;
        event.ready = true;
    }
    break;
    case WM_ERASEBKGND:
        // bProcessed = true;
        break;
    case WM_PAINT:
        event.msg = KK_WIN_MSG_PAINT;
        event.ready = true;
        break;
    case WM_MOUSEMOVE:
    {
        auto x = GET_X_LPARAM(lparam);
        auto y = GET_Y_LPARAM(lparam);
        event.msg = KK_WIN_MSG_MOUSE_MOVE;
        event.mouse_btn = KK_MOUSE_BTN_MOVE;
        event.pos.x = x;
        event.pos.y = y;
        event.ready = true;
    }
    break;
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
    {
        auto x = GET_X_LPARAM(lparam);
        auto y = GET_Y_LPARAM(lparam);
        event.msg = KK_WIN_MSG_MOUSE_KDOWN;
        if (msg == WM_LBUTTONDOWN)
        {
            event.mouse_btn = KK_MOUSE_BTN_L;
        }
        else if (msg == WM_MBUTTONDOWN)
        {
            event.mouse_btn = KK_MOUSE_BTN_MID;
        }
        else if (msg == WM_RBUTTONDOWN)
        {
            event.mouse_btn = KK_MOUSE_BTN_R;
        }
        event.pos.x = x;
        event.pos.y = y;
        event.ready = true;
    }
    break;
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    {
        auto x = GET_X_LPARAM(lparam);
        auto y = GET_Y_LPARAM(lparam);
        event.msg = KK_WIN_MSG_MOUSE_KUP;
        if (msg == WM_LBUTTONUP)
        {
            event.mouse_btn = KK_MOUSE_BTN_L;
        }
        else if (msg == WM_MBUTTONUP)
        {
            event.mouse_btn = KK_MOUSE_BTN_MID;
        }
        else if (msg == WM_RBUTTONUP)
        {
            event.mouse_btn = KK_MOUSE_BTN_R;
        }
        event.pos.x = x;
        event.pos.y = y;
        event.ready = true;
    }
    break;
        // 需要添加 DBCLICK flags 才可以响应
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDBLCLK:
    {
        auto x = GET_X_LPARAM(lparam);
        auto y = GET_Y_LPARAM(lparam);
        event.msg = KK_WIN_MSG_MOUSE_DBCLICK;
        if (msg == WM_LBUTTONDBLCLK)
        {
            event.mouse_btn = KK_MOUSE_BTN_L;
        }
        else if (msg == WM_MBUTTONDBLCLK)
        {
            event.mouse_btn = KK_MOUSE_BTN_MID;
        }
        else if (msg == WM_RBUTTONDBLCLK)
        {
            event.mouse_btn = KK_MOUSE_BTN_R;
        }
        event.pos.x = x;
        event.pos.y = y;
        event.ready = true;
    }
    break;
    case WM_MOUSEWHEEL:
    {
        auto x = GET_X_LPARAM(lparam);
        auto y = GET_Y_LPARAM(lparam);
        event.msg = KK_WIN_MSG_MOUSE_WHEEL;
        event.mouse_btn = KK_MOUSE_BTN_WHEEL;
        event.value = GET_WHEEL_DELTA_WPARAM(wparam);
        event.pos.x = x - m_rect.x1;
        event.pos.y = y - m_rect.y1;
        event.ready = true;
    }
    break;
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
    {
        event.msg = KK_WIN_MSG_KBD_DOWN;
        event.value = LOWORD(wparam);
        // key state
        event.sys = KK_KBD_SYS_NONE;
        if (GetKeyState(VK_CONTROL) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_CTRL;
        }
        if (GetKeyState(VK_SHIFT) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_SHIFT;
        }
        if (GetKeyState(VK_MENU) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_ALT;
        }
        if (GetKeyState(VK_LWIN) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_WIN;
        }
        if (GetKeyState(VK_RWIN) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_WIN;
        }
        event.ready = true;
    }
    break;
    case WM_KEYUP:
    case WM_SYSKEYUP:
    {
        event.msg = KK_WIN_MSG_KBD_UP;
        event.value = LOWORD(wparam);
        // key state
        event.sys = KK_KBD_SYS_NONE;
        if (GetKeyState(VK_CONTROL) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_CTRL;
        }
        if (GetKeyState(VK_SHIFT) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_SHIFT;
        }
        if (GetKeyState(VK_MENU) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_ALT;
        }
        if (GetKeyState(VK_LWIN) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_WIN;
        }
        if (GetKeyState(VK_RWIN) & 0x8000)
        {
            event.sys |= KK_KBD_SYS_WIN;
        }
        event.ready = true;
    }
    break;
    case WM_CHAR:
    case WM_SYSCHAR:
    {
        event.msg = KK_WIN_MSG_KBD_CHAR;
        event.value = LOWORD(wparam);
        event.ready = true;
    }
    break;
    // ime
    case WM_IME_STARTCOMPOSITION:
    {
        // if (mgr->pCurFocusUI == nullptr || mgr->pCurFocusUI == mgr->pRoot) {
        //   break;
        // }
        // 控制 ime cadia 显示位置
        HIMC hIMC = ImmGetContext(hwnd);
        if (hIMC)
        {
            POINT point;
            GetCaretPos(&point);
            printf("GetCaretPos: %d, %d\n", point.x, point.y);
            COMPOSITIONFORM Composition;
            Composition.dwStyle = CFS_POINT;
            Composition.ptCurrentPos.x = point.x;
            Composition.ptCurrentPos.y = point.y;
            ImmSetCompositionWindow(hIMC, &Composition);
            // font
            // if (mgr->pCurFocusUI != nullptr) {
            // auto pFocus = (UIContainer *)mgr->pCurFocusUI;
            // 设置输入法字体
            // ImmSetCompositionFontW(hIMC, nullptr);
            // }
            ImmReleaseContext(hwnd, hIMC);
        }
    }
    break;
    case WM_IME_CHAR:
    {
        event.msg = KK_WIN_MSG_IME_CHAR;
        event.value = LOWORD(wparam);
        event.ready = true;
    }
    break;
    case WM_ACTIVATEAPP:
    {
        event.msg = KK_WIN_MSG_ACTIVATE;
        event.value = LOWORD(wparam);
        event.ready = true;

        m_is_active = LOWORD(wparam) != 0;
    }
    break;
    case WM_SHOWWINDOW:
    {
        event.msg = KK_WIN_MSG_SHOW;
        event.value = LOWORD(wparam);
        event.ready = true;

        m_is_show = LOWORD(wparam) != 0;
    }
    break;
    default:
        break;
    }
    if (event.ready)
    {
        g_app->OnEvent(this, event);
        // special logic
        if (event.msg == KK_WIN_MSG_CLOSE)
        {
            ::DestroyWindow(hwnd); // 允许关闭，并触发WM_DESTROY消息
        }
    }
    return bProcessed;
}

KKWindow::KKWindow() {}

KKWindow *KKWindow::CreateWin(int width, int height, const char *title, int flags)
{

    // instance
    if (g_instance == nullptr)
    {
        g_instance = ::GetModuleHandleW(nullptr);
    }
    if (g_app == nullptr)
    {
        printf("KKWindow Create failed, reason: g_app is nullptr.\r\n");
        return nullptr;
    }
    // create window
    std::string buf(title);
    auto uni_buf = text_utf8_to_utf16(title);
    // flags
    int win_flags = WS_OVERLAPPEDWINDOW;
    if (flags & KK_WIN_FLAG_FIXED)
    {
        win_flags &= ~(WS_MAXIMIZEBOX | WS_THICKFRAME);
    }
    // if (flags & KK_WIN_FLAG_CLOSEABLE) {
    //   win_flags &= ~(WS_SYSMENU & 0xF0000);
    // }
    if (flags & KK_WIN_FLAG_FRAMELESS)
    {
        win_flags = WS_POPUP;
    }

    // base window
    const auto baseClassName = L"KK_GUI_WIN_CLASS";
    _register_win_class((HINSTANCE)g_instance, baseClassName, _WndProc);
    auto hWnd = ::CreateWindowExW(0, baseClassName, uni_buf.c_str(), win_flags, CW_USEDEFAULT, 0, width, height, nullptr, nullptr,
                                  (HINSTANCE)g_instance, nullptr);
    if (!hWnd)
        return nullptr;

    RECT rc;
    GetWindowRect(hWnd, &rc);

    // registe WinManager
    KKWindow *win = new KKWindow();
    win->m_rect.x1 = rc.left;
    win->m_rect.y1 = rc.top;
    win->m_rect.x2 = rc.right;
    win->m_rect.y2 = rc.bottom;
    win->m_hwnd = hWnd;
    ::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)win);

    // update window
    ::UpdateWindow(hWnd);

    KKEvent event;
    event.msg = KK_WIN_MSG_CREATE;
    event.ready = true;
    g_app->OnEvent(win, event);

    return win;
};

void KKWindow::Move(int x, int y)
{
    m_rect.x2 = x + (m_rect.x2 - m_rect.x1);
    m_rect.y2 = y + (m_rect.y2 - m_rect.y1);
    m_rect.x1 = x;
    m_rect.y1 = y;
    ::SetWindowPos((HWND)m_hwnd, nullptr, x, y, 0, 0, SWP_NOSIZE);
}

void KKWindow::Resize(int width, int height)
{
    m_rect.x2 = m_rect.x1 + width;
    m_rect.y2 = m_rect.y1 + height;
    ::SetWindowPos((HWND)m_hwnd, nullptr, 0, 0, width, height, SWP_NOMOVE);
}

void KKWindow::SetTitle(const char *title)
{
    std::string buf(title);
    auto uni_buf = text_utf8_to_utf16(title);
    ::SetWindowTextW((HWND)m_hwnd, uni_buf.c_str());
}
void KKWindow::Show(bool show, bool isMinimezed)
{
    auto flags = SW_HIDE;
    if (show)
    {
        flags = SW_SHOWNORMAL;
        if (isMinimezed)
        {
            flags = SW_SHOWMINIMIZED;
        }
    }
    ::ShowWindow((HWND)m_hwnd, flags);
}
void KKWindow::MoveCenter()
{
    auto screenWidth = GetSystemMetrics(SM_CXSCREEN);
    auto screenHeight = GetSystemMetrics(SM_CYSCREEN);
    auto x = (screenWidth - (m_rect.x2 - m_rect.x1)) / 2;
    auto y = (screenHeight - (m_rect.y2 - m_rect.y1)) / 2;
    Move(x, y);
}

void KKWindow::SetIcon(const char *path)
{
    std::string buf(path);
    auto uni_buf = text_utf8_to_utf16(path);
    auto icon = ::LoadImageW(nullptr, uni_buf.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
    ::SendMessageW((HWND)m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
    ::SendMessageW((HWND)m_hwnd, WM_SETICON, ICON_BIG, (LPARAM)icon);
}

void KKWindow::SetCursor(KK_CURSOR_STYLE cursor)
{
    HCURSOR hCursor = nullptr;
    switch (cursor)
    {
    case KK_CURSOR_STYLE_ARROW:
        hCursor = ::LoadCursor(nullptr, IDC_ARROW);
        break;
    case KK_CURSOR_STYLE_IBEAM:
        hCursor = ::LoadCursor(nullptr, IDC_IBEAM);
        break;
    case KK_CURSOR_STYLE_WAIT:
        hCursor = ::LoadCursor(nullptr, IDC_WAIT);
        break;
    case KK_CURSOR_STYLE_CROSS:
        hCursor = ::LoadCursor(nullptr, IDC_CROSS);
        break;
    case KK_CURSOR_STYLE_UPARROW:
        hCursor = ::LoadCursor(nullptr, IDC_UPARROW);
        break;
    case KK_CURSOR_STYLE_SIZENWSE:
        hCursor = ::LoadCursor(nullptr, IDC_SIZENWSE);
        break;
    case KK_CURSOR_STYLE_SIZENESW:
        hCursor = ::LoadCursor(nullptr, IDC_SIZENESW);
        break;
    case KK_CURSOR_STYLE_SIZEWE:
        hCursor = ::LoadCursor(nullptr, IDC_SIZEWE);
        break;
    case KK_CURSOR_STYLE_SIZENS:
        hCursor = ::LoadCursor(nullptr, IDC_SIZENS);
        break;
    case KK_CURSOR_STYLE_SIZEALL:
        hCursor = ::LoadCursor(nullptr, IDC_SIZEALL);
        break;
    case KK_CURSOR_STYLE_NO:
        hCursor = ::LoadCursor(nullptr, IDC_NO);
        break;
    case KK_CURSOR_STYLE_HAND:
        hCursor = ::LoadCursor(nullptr, IDC_HAND);
        break;
    case KK_CURSOR_STYLE_APPSTARTING:
        hCursor = ::LoadCursor(nullptr, IDC_APPSTARTING);
        break;
    case KK_CURSOR_STYLE_HELP:
        hCursor = ::LoadCursor(nullptr, IDC_HELP);
        break;
    default:
        break;
    }

    if (hCursor)
    {
        ::SetCursor(hCursor);
    }
}

void KKWindow::SetTransparent(unsigned char alpha)
{
    auto exStyle = ::GetWindowLongPtrW((HWND)m_hwnd, GWL_EXSTYLE);
    ::SetWindowLongPtrW((HWND)m_hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
    ::SetLayeredWindowAttributes((HWND)m_hwnd, 0, alpha, LWA_ALPHA);
}

void KKWindow::SetFocus() { ::SetFocus((HWND)m_hwnd); }

void *KKWindow::GetWinHandle() { return m_hwnd; }

void KKWindow::SetUITree(KKUIBase *base) { m_uiBase = base; }
KKUIBase *KKWindow::GetUITree() { return m_uiBase; }

void KKWindow::SetRender(KKRenderI *render) { m_render = render; }
KKRenderI *KKWindow::GetRender() { return m_render; }